#ifndef PROCESSPOOL_HPP
#define PROCESSPOOL_HPP
#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include "Task.hpp"
#include <sys/wait.h>
// 先描述
class Channel // 对一个通道的管理
{
public:
    Channel(int fd, pid_t id) // 构造
        : _wfd(fd), _subid(id)
    {
        // 打印下信息
        _name = "channel" + std::to_string(_wfd) + '-' + std::to_string(_subid); // to_string就是将整数转换为字符串
    }
    ~Channel() // 析构
    {
    }
    void Send(int code)
    {
        int n = write(_wfd, &code, sizeof(code)); // 将信息发过去
        (void)n;                                  // 我们这里强转下，因为如果不加这个代码的话，那么我们的n就没有被使用过，绕过编译器对未使用过的变量的一个检查操作
    }
    void Close()
    {
        close(_wfd); // 关闭文件描述符
    }
    void Wait()
    {
        pid_t rid = waitpid(_subid, NULL, 0); // 等待子进程结束
        (void)rid;                            // 我们这里强转下，因为如果不加这个代码的话，那么我们的rid就没有被使用过，绕过编译器对未使用过的变量的一个检查操作
    }
    int Fd() { return _wfd; }            // 获取文件描述符
    pid_t Subid() { return _subid; }     // 获取子进程的id
    std::string Name() { return _name; } // 获取信道的名字

private:
    int _wfd;          // 文件描述符  ，我们往这个_wfd中写数据，那么父进程就可以往子进程中写数据了
    pid_t _subid;      // 子进程的id
    std::string _name; // 这个信道的名字
    // int _loadnum;
};
// 再组织
class ChannelManager
{
public:
    ChannelManager()
        : _next(0) // 初始化为0
    {
    }
    void Insert(int wfd, pid_t subid) // 构建一个通信信道，然后将这个通信通道push到vector中
    {
        _channels.emplace_back(wfd, subid); // emplace_back是直接在vector中构造对象，避免了拷贝构造
        // Channel c(wfd,subid);//构建一个通信信道
        // _channels.push_back(c);//将这个通信信道push到vector中，将C这个通信信道插入到vector _channels中
    }
    Channel &Select()
    {
        auto &c = _channels[_next]; // 选择一个信道，默认从第一个信道开始，因为我们初始化为0的
        _next++;                    // 下一个信道的下标
        _next %= _channels.size();  // 如果下标超过了vector的大小，那么就重新回到0
        return c;                   // 返回这个信道
    }
    void PrintChannels() // 打印所有的通信信道
    {
        for (auto &channel : _channels) // 遍历我们vector _channels中的所有通道
        {
            std::cout << channel.Name() << std::endl;
        }
    }
    void CloseAll() // 关闭所有的通信信道
    {
        for (auto &channel : _channels) // 遍历我们vector _channels中的所有通道
        {
            channel.Close(); // 关闭这个信道,每个信道调用close关闭文件描述符
            
        }
    }
    void StopSubProcess()
    {
        for (auto &channel : _channels) // 遍历我们vector _channels中的所有通道
        {
            channel.Close(); // 关闭这个信道,每个信道调用close关闭文件描述符
            std::cout << "关闭信道" << channel.Name() << std::endl;
        }
    }
    void WaitSubProcess()
    {
        for (auto &channel : _channels) // 遍历我们vector _channels中的所有通道
        {
            channel.Wait(); // 等待子进程结束
            std::cout << "回收子进程结束" << channel.Name() << std::endl;
        }
    }
    void CloseAndWait()
    {
        for(auto& channel:_channels)//遍历我们vector _channels中的所有通道
        {
            channel.Close();//关闭这个信道,每个信道调用close关闭文件描述符
            std::cout<<"关闭信道"<<channel.Name()<<std::endl;
            channel.Wait();//等待子进程结束
            std::cout<<"回收子进程结束"<<channel.Name()<<std::endl;
        }

        // 解决方案1：倒着回收子进程
        // for (int i = _channels.size() - 1; i >= 0; i--) // 我们倒着回收子进程
        // {
        //     _channels[i].Close(); // 关闭这个信道,每个信道调用close关闭文件描述符
        //     std::cout << "关闭信道" << _channels[i].Name() << std::endl;
        //     _channels[i].Wait(); // 等待子进程结束
        //     std::cout << "回收子进程结束" << _channels[i].Name() << std::endl;
        // }

        //解决方案二：真的让父进程一个人指向所有的管道w端


    }
    ~ChannelManager()
    {
    }

private:
    // 父进程对通信信道的管理其实就是对这个vector进行管理
    std::vector<Channel> _channels; // 一个通道管理多个通道
    int _next;                      // 下一个要选择的信道的下标
};

const int gdefalutnum = 5; // 默认的通道数量
class ProcessPool          // 进程池
{
public:
    ProcessPool(int num)
        : _process_num(num) // 初始化
    {
        _tm.Register(PrintLog); // 注册一个任务，打印日志
        _tm.Register(DownLoad); // 注册一个任务，下载文件
        _tm.Register(Upload);   // 注册一个任务，上传文件
    }
    void Work(int rfd) // 子进程工作函数
    {
        while (true) // 子进程应该进行等待，直到父进程给
        {
            int code = 0; // 任务代码
            // rfd是文件描述符
            ssize_t n = read(rfd, &code, sizeof(code)); // 从管道中读取任务代码
            if (n > 0)                                  // 说明读取成功了
            {
                // 读取成功的话，我们就进行任务处理
                if (n != sizeof(code)) // 如果返回值不等于4的话，那么就说明读取的是不符合规范的
                {
                    continue;
                }
                std::cout << "子进程" << getpid() << "收到任务码" << code << std::endl;
                // 走到这里的话说明是规范的
                _tm.Execute(code); // 将任务码放入信道，让信道去完成任务
            }
            else if (n == 0) // 说明服务器将写端关闭了，那么我们就会读到0
            {
                std::cout << "子进程退出" << std::endl;
                break;
            }
            else
            {
                std::cout << "读管道失败" << std::endl;
                break;
            }
        }
    }
    bool Creat() // 创建进程池
    {
        // 只要我们Creat成功了，我们父进程对应的ProcessPool，他手里就有一个ChannelManager，
        // ChannelManager里面包含了一个vector，vector里面有多个Channel，每个Channel对应一个子进程
        for (int i = 0; i < _process_num; i++) // 这个for循环的话只有父进程能运行，子进程是由fork创建的，子进程只会进行自己的Work()
        {
            // 1.创建管道
            int pipefd[2] = {0};  // 默认初始化为0
            int n = pipe(pipefd); // 创建管道
            if (n < 0)
                return false; // 管道创建失败了

            // 2.创建子进程
            pid_t subid = fork(); // 创建子进程
            if (subid < 0)
                return false; // 子进程创建失败了
            else if (subid == 0)
            {
                //让子进程关闭自己继承下来的，他的哥哥进程的w端关闭就行了
                _cm.CloseAll(); // 关闭所有的信道
                //这个_cm就是父进程历史打开的文件描述符的写端，我们需要关闭这个写端，让子进程的读端来进行读操作

                // 关闭不需要的文件描述符
                // 我们是需要进行父进程的写入操作的，子进程去读的
                close(pipefd[1]); // 关闭写端
                // 子进程
                Work(pipefd[0]);  // 子进程去读管道
                close(pipefd[0]); // 关闭读端
                exit(0);          // 子进程退出
            }
            else
            {
                // 父进程
                close(pipefd[0]);             // 关闭读端
                _cm.Insert(pipefd[1], subid); // 构建一个通信信道,第一个参数是文件描述符，第二个是子进程的id
                // 写端pipefd[1]
            }
        }
        return true;
    }
    void Debug()
    {
        _cm.PrintChannels(); // 将所有的通信信道打印出来
    }

    void Run() // 将任务代码放入信道
    {
        // 选择一个任务
        int taskcode = _tm.Code(); // 选择一个任务获取任务码

        // 选择一个信道[子进程]，负载均衡的选择一个子进程，完成任务
        auto &c = _cm.Select(); // 从这个管理器里面选择一个管道去完成任务

        // 挑好了信道，发送任务
        c.Send(taskcode); // 发送任务代码
    }
    void Stop()
    {
        // //关闭父进程的wfd就行了
        // _cm.StopSubProcess();

        // //回收所有的子进程
        // _cm.WaitSubProcess();
        _cm.CloseAndWait(); // 关闭所有的信道，并回收所有的子进程
    }
    ~ProcessPool() // 析构
    {
    }

private: 
    ChannelManager _cm; // 通信信道管理器(就是将单个管道进行集中管理的)
    int _process_num;   // 进程数量,需要告诉我们想创建多少个进程池
    TaskManager _tm;    // 任务管理器，管理任务的分配和执行
};

#endif // PROCESSPOOL_HPP